﻿using UnityEngine;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System;
using System.Threading;
using System.IO;
using NetWork.Packets;
using NetWork.Packets.Factory;

namespace NetWork.Layer
{
    public enum SocketState : int
    {
        NONE,
        CREATE,
        CONNECTING,
        WORKING,
        CLOSE,
        ERROR,
    }
    public class SocketTool
    {
        private Queue<Packet> receivePackage;
        private Queue<ErrorPacket> receiveError;
        private byte[] receiveBuffer;
        private Socket socket;
        private SocketState state;
        private string address;
        private int port;
        private int sendTimeout;
        private string name;
        public string Name
        {
            get
            {
                return name;
            }
        }

        public bool Connected
        {
            get
            {
                if (socket == null)
                {
                    return false;
                }
                else
                {
                    return socket.Connected;
                }
            }
        }

        public SocketState State
        {
            get
            {
                return state;
            }
        }

        public bool connJustComplete = false;
        public SocketTool(string name, string address, int port, int timeout = 15000)
        {
            this.name = name;
            this.address = address;
            this.port = port;
            this.sendTimeout = timeout;
            receivePackage = new Queue<Packet>();
            receiveError = new Queue<ErrorPacket>();
            state = SocketState.CREATE;
            receiveBuffer = new byte[1 << 16];
            //Debug.Log(1 << 16);
            Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
        }
        public void Connect()
        {
            Clear();
            Debug.Log("#################connect to server: ip " + address + " port: " + port + "##############");
			IPAddress ia = IPAddress.Parse (address);
			if (YCPlugin.IsSupportIpv4(address)) {
				socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			} else {
                ia = IPAddress.Parse (YCPlugin.GetIpv6(address));
				socket = new Socket (ia.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
			}
            socket.Blocking = true;
            socket.SendTimeout = sendTimeout;
            socket.ReceiveTimeout = 0;
            try
            {
                state = SocketState.CONNECTING;
                socket.BeginConnect(ia, port, ConnectHandler, socket);
            }
            catch (SocketException e)
            {
                state = SocketState.ERROR;
                Debug.LogError(e);
            }
        }

		private string GetIpv6(string ipv4Host)
		{
			var split = ipv4Host.Split('.');
			var ipv4 = new int[4];
			for (int i = 0; i < 4; i++)
			{
				ipv4[i] = int.Parse(split[i]);
			}
			string ipv6 = string.Format("64:ff9b::{0}{1}:{2}{3}",
				Convert.ToString(ipv4[0], 16),
				Convert.ToString(ipv4[1], 16),
				Convert.ToString(ipv4[2], 16),
				Convert.ToString(ipv4[3], 16)
			);
			return ipv6;
		}

        private void ConnectHandler(IAsyncResult async)
        {
            try
            {
                Socket s = (Socket)async.AsyncState;
                if (s.Connected)
                {
                    s.EndConnect(async);
                    state = SocketState.WORKING;
//#if UNITY_IPHONE
//                    if(receiveThread != null && receiveThread.IsAlive)
//                    {
//                        receiveThread.Abort();
//                    }
//                    receiveThread = new Thread(Receive);
//                    receiveThread.Start();
                    
//#else
                    ReceiveHelper ah = new ReceiveHelper();
                    ah.socket = s;
                    ah.isHeader = true;
                    ah.receivedLength = 0;
                    ah.requiredLength = 4;
                    s.BeginReceive(receiveBuffer, 0, 4, SocketFlags.None, ReceiveHandler, ah);
//#endif
                }
                else
                {
                    state = SocketState.ERROR;
                    Debug.LogError("Socket connect Error");
                }
            }
            catch (Exception e)
            {
                Debug.LogError("Connect error,Exception " + e);
                state = SocketState.ERROR;
            }
            connJustComplete = true;
        }
        private Thread receiveThread;
        private int receivedLength = 0;
        private int needReceiveLength = 4;
        private bool isHeader = true;
        public void Receive()
        {
            try
            {
                receivedLength = 0;
                needReceiveLength = 4;
                isHeader = true;
                while (true)
                {
                    //List<Socket> list = new List<Socket>();
                    //list.Add(socket);
                    //Socket.Select(list, null, null, 33);
                    //if (list.Count > 0)
                    //{

                        int rNum = socket.Receive(receiveBuffer, receivedLength, needReceiveLength - receivedLength, SocketFlags.None);
                        if (rNum == 0)
                        {
                            socket.Close();
                            state = SocketState.CLOSE;
                            Debug.Log("Socket receive length 0,socket closed ");
                            return;
                        }
                        receivedLength += rNum;
                        if (receivedLength == needReceiveLength)
                        {
                            if (isHeader)
                            {
                                //TODO 解析头
                                int length = BitConverter.ToInt32(receiveBuffer, 0);
                                length = IPAddress.NetworkToHostOrder(length) + 4;
                                isHeader = false;
                                receivedLength = 0;
                                needReceiveLength = length;
                            }
                            else
                            {
                                //解析包
                                int nOpcode;
                                object body;
                                if (PacketFactory.Bytes2PB(receiveBuffer, needReceiveLength - 4, out nOpcode, out body))
                                {
                                    Packet package = new Packet(nOpcode, body);
#if UNITY_EDITOR
                                    Debug.Log("receive socket message " + nOpcode);
#endif
                                    ReceiveNewMessage(package);
                                }
                                else
                                {
                                    ErrorPacket ep = new ErrorPacket();
                                    ep.errorCode = 1004;
                                    ReceiveError(ep);
                                    Debug.LogError("socket 解析错误");
                                }
                                isHeader = true;
                                receivedLength = 0;
                                needReceiveLength = 4;
                            }
                        }
                    //}
                }

            }
            catch (Exception e)
            {
                socket.Close();
                state = SocketState.ERROR;
                Debug.Log("Socket receive error " + e);
            }
        }

        private void ReceiveHandler(IAsyncResult async)
        {
            ReceiveHelper ah = (ReceiveHelper)async.AsyncState;
            Socket s = ah.socket;
            if (!s.Connected)
            {
                return;
            }
            try
            {
                int read = s.EndReceive(async);
                if (read > 0)
                {
                    ah.receivedLength += read;
                    if (ah.receivedLength > ah.requiredLength)
                    {
                        Debug.LogError("something error in socket!");
                        s.Close();
                        state = SocketState.ERROR;
                        return;
                    }
                    else if (ah.receivedLength == ah.requiredLength)//接收完一次合理的数据
                    {
                        if (ah.isHeader)
                        {
                            int length = BitConverter.ToInt32(receiveBuffer, 0);
                            length = IPAddress.NetworkToHostOrder(length) + 4;
                            ah.isHeader = false;
                            ah.receivedLength = 0;
                            ah.requiredLength = length;
#if UNITY_EDITOR
                            if(length > 8000)
                            {
                                Debug.Log("<color=yellow>message need length " + length + "</color>");
                            }
#endif
                            s.BeginReceive(receiveBuffer, 0, length, SocketFlags.None, ReceiveHandler, ah);
                        }
                        else
                        {
                            int nOpcode;
                            object body;
                            if (PacketFactory.Bytes2PB(receiveBuffer, ah.requiredLength - 4, out nOpcode, out body))
                            {
                                Packet package = new Packet(nOpcode, body);
#if UNITY_EDITOR
                                Debug.Log("receive socket message " + nOpcode);
#endif
                                ReceiveNewMessage(package);
                            }
                            else
                            {
                                ErrorPacket ep = new ErrorPacket();
                                ep.errorCode = 1004;
                                ReceiveError(ep);
                                Debug.LogError("socket 解析错误");
                            }
                            ah.isHeader = true;
                            ah.receivedLength = 0;
                            ah.requiredLength = 4;
                            s.BeginReceive(receiveBuffer, 0, 4, SocketFlags.None, ReceiveHandler, ah);
                        }
                    }
                    else
                    {
                        s.BeginReceive(receiveBuffer, ah.receivedLength, ah.requiredLength - ah.receivedLength, SocketFlags.None, ReceiveHandler, ah);
                    }
                }
                else
                {
                    s.Close();
                    Debug.Log("remote close the socket");
                    state = SocketState.CLOSE;
                }
            }
            catch (Exception e)
            {
                Debug.LogError("Exception happens in socket receive data. Exception " + e);
                s.Close();
                state = SocketState.ERROR;
            }

        }

        public void Clear()
        {
            if (socket != null)
            {
                if (socket.Connected)
                {
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();
                }
                socket = null;
            }
            if (receiveBuffer != null)
            {
                Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
            }
            if (receivePackage != null)
            {
                receivePackage.Clear();
            }
            if (receiveError != null)
            {
                receiveError.Clear();
            }
            state = SocketState.CREATE;
        }

        public void Reconnect(string address,int port)
        {
            this.address = address;
            this.port = port;
            Connect();
        }

        private void ReceiveError(ErrorPacket errorPacket)
        {
            lock (errorPacket)
            {
                receiveError.Enqueue(errorPacket);
            }
        }

        private void ReceiveNewMessage(Packet message)
        {
            lock (receivePackage)
            {
                receivePackage.Enqueue(message);
            }
        }

        public Packet CheckReceivePacket()
        {
            if (receivePackage == null)
            {
                return null;
            }
            else
            {
                lock (receivePackage)
                {
                    if (receivePackage.Count == 0)
                    {
                        return null;
                    }
                    else
                    {
                        return receivePackage.Dequeue();
                    }
                }
            }
        }

        public ErrorPacket CheckError()
        {
            if (receiveError == null)
            {
                return null;
            }
            else
            {
                lock (receiveError)
                {
                    if (receiveError.Count == 0)
                    {
                        return null;
                    }
                    else
                    {
                        return receiveError.Dequeue();
                    }
                }
            }
        }

        public bool SendMessage(int msgId, object msg)
        {
#if UNITY_EDITOR
            //Debug.Log("Send message id " + msgId);
#endif
            if (state != SocketState.WORKING)
            {
                Debug.LogError("socket state error!");
                return false;
            }
            else
            {
                byte[] data;
                PacketFactory.PB2Bytes(msgId, msg, out data);
                SendHelper sh = new SendHelper();
                sh.data = data;
                sh.socket = socket;
                sh.sentNumber = 0;
                socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendHandler, sh);
                return true;
            }
        }

        public bool SendMessage(Packet packet)
        {
            return SendMessage(packet.nOpCode, packet.kBody);
        }

        private void SendHandler(IAsyncResult async)
        {
            SendHelper sh = (SendHelper)async.AsyncState;
            if (!sh.socket.Connected)
            {
                return;
            }
            try
            {
                int sendCount = sh.socket.EndSend(async);
                sh.sentNumber += sendCount;
                if (sh.sentNumber < sh.data.Length)
                {
                    socket.BeginSend(sh.data, sh.sentNumber, sh.data.Length - sh.sentNumber,
                        SocketFlags.None, SendHandler, sh);
                }
            }
            catch (Exception e)
            {
                Debug.LogError("End send error " + e);
                socket.Close();
                state = SocketState.ERROR;
            }
        }

        public void Close()
        {
            Clear();
        }

        internal class SendHelper
        {
            public Socket socket;
            public int sentNumber;
            public byte[] data;
        }

        internal class ReceiveHelper
        {
            public Socket socket;
            public int receivedLength;
            public int requiredLength;
            public bool isHeader;
        }
    }
}